package cn.kuporo.seckill.goods.application.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.kuporo.seckill.common.cache.distribute.DistributedCacheService;
import cn.kuporo.seckill.common.cache.local.LocalCacheService;
import cn.kuporo.seckill.common.cache.model.SeckillBusinessCache;
import cn.kuporo.seckill.common.constants.MQConstants;
import cn.kuporo.seckill.common.constants.RedisConstants;
import cn.kuporo.seckill.common.exception.ErrorCode;
import cn.kuporo.seckill.common.exception.SeckillException;
import cn.kuporo.seckill.common.model.dto.SeckillActivityDTO;
import cn.kuporo.seckill.common.model.dto.SeckillGoodsDTO;
import cn.kuporo.seckill.common.model.dto.SeckillStockDTO;
import cn.kuporo.seckill.common.model.enums.PlaceOrderType;
import cn.kuporo.seckill.common.model.enums.SeckillGoodsStatus;
import cn.kuporo.seckill.common.model.message.ErrorMessage;
import cn.kuporo.seckill.common.model.message.TxMessage;
import cn.kuporo.seckill.common.model.response.Response;
import cn.kuporo.seckill.common.utils.SnowFlakeFactory;
import cn.kuporo.seckill.common.utils.SystemClock;
import cn.kuporo.seckill.dubbo.interfaces.activity.SeckillActivityDubboService;
import cn.kuporo.seckill.dubbo.interfaces.stock.SeckillStockDubboService;
import cn.kuporo.seckill.goods.application.cacahe.SeckillGoodsCacheService;
import cn.kuporo.seckill.goods.application.cacahe.SeckillGoodsListCacheService;
import cn.kuporo.seckill.goods.application.service.SeckillGoodsService;
import cn.kuporo.seckill.goods.domain.model.SeckillGoods;
import cn.kuporo.seckill.goods.domain.model.vo.GoodsSaveReqVO;
import cn.kuporo.seckill.goods.domain.model.vo.GoodsUpdateStatusReqVO;
import cn.kuporo.seckill.goods.domain.service.SeckillGoodsDomainService;
import cn.kuporo.seckill.mq.MessageSenderService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author: kuporo
 * @since: 2025/7/7
 * @description:
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class SeckillGoodsServiceImpl implements SeckillGoodsService {
    private final SeckillGoodsDomainService seckillGoodsDomainService;
    private final SeckillGoodsListCacheService seckillGoodsListCacheService;
    private final SeckillGoodsCacheService seckillGoodsCacheService;
    private final DistributedCacheService distributedCacheService;
    private final LocalCacheService<String, SeckillGoods> localCacheService;
    private final MessageSenderService messageSenderService;


    @DubboReference
    private SeckillActivityDubboService seckillActivityDubboService;
    @DubboReference
    private SeckillStockDubboService seckillStockDubboService;
    @Value("${place.order.type:lua}")
    private String placeOrderType;

    /**
     * 保存商品信息
     */
    @Override
    @Transactional
    public Response<?> saveSeckillGoods(GoodsSaveReqVO goodsSaveReqVO) {
        SeckillActivityDTO seckillActivityDTO = seckillActivityDubboService.getSeckillActivity(goodsSaveReqVO.getActivityId(), goodsSaveReqVO.getVersion());
        if (seckillActivityDTO == null){
            throw new SeckillException(ErrorCode.ACTIVITY_NOT_EXISTS);
        }
        SeckillGoods seckillGoods = new SeckillGoods();
        BeanUtils.copyProperties(goodsSaveReqVO, seckillGoods);
        seckillGoods.setStartTime(seckillActivityDTO.getStartTime());
        seckillGoods.setEndTime(seckillActivityDTO.getEndTime());
        seckillGoods.setAvailableStock(goodsSaveReqVO.getInitialStock());
        seckillGoods.setId(SnowFlakeFactory.getSnowFlakeFromCache().nextId());
        seckillGoods.setStatus(SeckillGoodsStatus.PUBLISHED.getCode());
        //将商品的库存同步到Redis
        distributedCacheService.put(RedisConstants.getKey(RedisConstants.GOODS_ITEM_STOCK_KEY_PREFIX, seckillGoods.getId()), seckillGoods.getAvailableStock());
        //商品限购同步到Redis
        distributedCacheService.put(RedisConstants.getKey(RedisConstants.GOODS_ITEM_LIMIT_KEY_PREFIX, seckillGoods.getId()), seckillGoods.getLimitNum());
        seckillGoodsDomainService.saveSeckillGoods(seckillGoods);
        return Response.success();
    }

    @Override
    public SeckillGoods getSeckillGoodsById(Long id) {
        // 实现多级缓存，先从本地缓存获取
        String key = RedisConstants.getKey(RedisConstants.GOODS_ITEM_KEY_PREFIX, id);
        // 从本地缓存获取数据
        SeckillGoods seckillGoods =  localCacheService.getIfPresent(key);
        if (seckillGoods == null){
            // 从Redis获取数据
            seckillGoods = distributedCacheService.getObject(key, SeckillGoods.class);
            // 从Redis获取的数据为空
            if (seckillGoods == null){
                // 数据库中获取数据
                seckillGoods = seckillGoodsDomainService.getSeckillGoodsId(id);
                if (seckillGoods != null){
                    // 处理分桶库存
                    seckillGoods = this.getSeckillGoods(seckillGoods);
                    // 将数据缓存到Redis
                    distributedCacheService.put(key, seckillGoods, 10, TimeUnit.MINUTES);
                }
                if (seckillGoods != null){
                    localCacheService.put(key, seckillGoods);
                }
            }
        }
        return seckillGoods;

    }

    /**
     * 兼容分桶库存
     */
    private SeckillGoods getSeckillGoods(SeckillGoods seckillGoods) {
        //不是分桶库存模式
        if (!PlaceOrderType.BUCKET.getType().equals(placeOrderType)){
            return seckillGoods;
        }
        // 是分桶库存模式，获取分桶库存
        SeckillBusinessCache<SeckillStockDTO> seckillStockCache = seckillStockDubboService.getSeckillStock(seckillGoods.getId(), 1L);
        if (seckillStockCache == null || !seckillStockCache.isExist() || seckillStockCache.isRetryLater() || seckillStockCache.getData() == null){
            return seckillGoods;
        }
        seckillGoods.setInitialStock(seckillStockCache.getData().getTotalStock());
        seckillGoods.setAvailableStock(seckillStockCache.getData().getAvailableStock());
        return seckillGoods;
    }

    /**
     * 根据活动id获取商品列表
     */
    @Override
    public List<SeckillGoods> getSeckillGoodsByActivityId(Long activityId) {
        List<SeckillGoods> seckillGoodsList = seckillGoodsDomainService.getSeckillGoodsByActivityId(activityId);
        if (CollUtil.isEmpty(seckillGoodsList)){
            return Collections.emptyList();
        }
        return seckillGoodsList.stream().map(this::getSeckillGoods).collect(Collectors.toList());
    }

    /**
     * 清空缓存的商品数据
     */
    private void clearCache(String id){
        //清除缓存中的商品库存
        distributedCacheService.delete(RedisConstants.getKey(RedisConstants.GOODS_ITEM_STOCK_KEY_PREFIX, id));
        //清除本地缓存中的商品
        localCacheService.delete(RedisConstants.getKey(RedisConstants.GOODS_ITEM_KEY_PREFIX, id));
        //清除Redis缓存中的商品
        distributedCacheService.delete(RedisConstants.getKey(RedisConstants.GOODS_ITEM_KEY_PREFIX, id));
        //清除商品的限购信息
        distributedCacheService.delete(RedisConstants.getKey(RedisConstants.GOODS_ITEM_LIMIT_KEY_PREFIX, id));
    }

    @Override
    @Transactional
    public Response<?> updateStatus(GoodsUpdateStatusReqVO goodsUpdateStatusReqVO) {
        Long id = goodsUpdateStatusReqVO.getId();
        Integer status = goodsUpdateStatusReqVO.getStatus();
        if (status.equals(SeckillGoodsStatus.OFFLINE.getCode())){
            //清空缓存
            this.clearCache(String.valueOf(id));
        }
        seckillGoodsDomainService.updateStatus(id, status);
        return Response.success();
    }

    /**
     * 从缓存中获取商品列表
     * @param activityId
     * @param version
     * @return
     */
    @Override
    public List<SeckillGoodsDTO> getSeckillGoodsList(Long activityId, Long version) {
        SeckillBusinessCache<List<SeckillGoods>> seckillGoodsListCache = seckillGoodsListCacheService.getCachedGoodsList(activityId, version);
        if (!seckillGoodsListCache.isExist()){
            throw new SeckillException(ErrorCode.ACTIVITY_NOT_EXISTS);
        }
        //稍后再试，前端需要对这个状态做特殊处理，即不去刷新数据，静默稍后再试
        if (seckillGoodsListCache.isRetryLater()){
            throw new SeckillException(ErrorCode.RETRY_LATER);
        }
        return seckillGoodsListCache.getData().stream().map((seckillGoods) -> {
            SeckillGoodsDTO seckillGoodsDTO = BeanUtil.copyProperties(seckillGoods, SeckillGoodsDTO.class);
            seckillGoodsDTO.setVersion(seckillGoodsListCache.getVersion());
            return seckillGoodsDTO;
        }).collect(Collectors.toList());
    }

    @Override
    public SeckillGoodsDTO getSeckillGoods(Long id, Long version) {
        SeckillBusinessCache<SeckillGoods> seckillGoodsCache = seckillGoodsCacheService.getSeckillGoods(id, version);
        //缓存中不存在商品数据
        if (!seckillGoodsCache.isExist()){
            throw new SeckillException(ErrorCode.ACTIVITY_NOT_EXISTS);
        }
        //稍后再试，前端需要对这个状态做特殊处理，即不去刷新数据，静默稍后再试
        if (seckillGoodsCache.isRetryLater()){
            throw new SeckillException(ErrorCode.RETRY_LATER);
        }
        SeckillGoodsDTO seckillGoodsDTO = BeanUtil.toBean(seckillGoodsCache.getData(), SeckillGoodsDTO.class);
        seckillGoodsDTO.setVersion(SystemClock.millisClock().now());
        return seckillGoodsDTO;
    }

    @Override
    public boolean updateAvailableStock(Integer count, Long id) {
        return seckillGoodsDomainService.updateAvailableStock(count, id);
    }
    @Override
    public boolean updateDbAvailableStock(Integer count, Long id) {
        return seckillGoodsDomainService.updateDbAvailableStock(count, id);
    }

    @Override
    @Transactional
    public boolean updateAvailableStock(TxMessage txMessage) {
        Boolean decrementStock = distributedCacheService.hasKey(RedisConstants.getKey(RedisConstants.GOODS_TX_KEY, txMessage.getTxNo()));
        if (Boolean.TRUE.equals(decrementStock)){
            log.info("updateAvailableStock|秒杀商品微服务已经扣减过库存|全局事务编号:{}", txMessage.getTxNo());
            return true;
        }
        boolean isUpdate = false;
        try{
            isUpdate = seckillGoodsDomainService.updateAvailableStock(txMessage.getQuantity(), txMessage.getGoodsId());
            if (isUpdate){
                // 扣减成功 添加事务表示防止重复扣减
                distributedCacheService.put(RedisConstants.getKey(RedisConstants.GOODS_TX_KEY, txMessage.getTxNo()), txMessage.getTxNo(), RedisConstants.TX_LOG_EXPIRE_DAY, TimeUnit.DAYS);
            }else {
                //发送失败消息给订单微服务
                messageSenderService.send(getErrorMessage(txMessage));
            }
        }catch (Exception e){
            isUpdate = false;
            log.error("updateAvailableStock|抛出异常|{}|{}",txMessage.getTxNo(), e.getMessage());
            //发送失败消息给订单微服务
            messageSenderService.send(getErrorMessage(txMessage));
        }
        return isUpdate;
    }

    @Override
    public SeckillBusinessCache<Integer> getAvailableStock(Long goodsId, Long version) {
        return seckillGoodsCacheService.getAvailableStock(goodsId, version);

    }

    /**
     * 发送给订单微服务的错误消息
     */
    private ErrorMessage getErrorMessage(TxMessage txMessage){
        return new ErrorMessage(MQConstants.TOPIC_ERROR_MSG, txMessage.getTxNo(), txMessage.getGoodsId(), txMessage.getQuantity(), txMessage.getPlaceOrderType(), txMessage.getException(), txMessage.getBucketSerialNo(), txMessage.getUserId(), txMessage.getOrderTaskId());

    }

    @Override
    public Integer getAvailableStockById(Long id) {
        return seckillGoodsDomainService.getAvailableStockById(id);
    }

}
